package org.apache.fop.render.intermediate;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Stack;

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.area.Block;
import org.apache.fop.area.BlockViewport;
import org.apache.fop.area.BodyRegion;
import org.apache.fop.area.LineArea;
import org.apache.fop.area.MainReference;
import org.apache.fop.area.NormalFlow;
import org.apache.fop.area.Page;
import org.apache.fop.area.PageSequence;
import org.apache.fop.area.PageViewport;
import org.apache.fop.area.RegionViewport;
import org.apache.fop.area.inline.InlineArea;
import org.apache.fop.area.inline.InlineParent;
import org.apache.fop.area.inline.TextArea;
import org.apache.fop.area.inline.WordArea;
import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.layoutmgr.inline.CustomTextLayoutManager;

import com.thunisoft.converter.fop.RenderEvent;
import com.thunisoft.converter.fop.RenderEventListener;

public class CustomIFRenderer extends IFRenderer {

    public static final ThreadLocal<FontTriplet> originFont = new ThreadLocal<FontTriplet>();

    private RenderEventListener eventListener;

    private Stack<RenderEvent> eventStack;

    private RenderEvent rootEvent;

    public CustomIFRenderer(FOUserAgent userAgent) {
        super(userAgent);
    }

    public RenderEventListener getEventListener() {
        return eventListener;
    }

    public void setEventListener(RenderEventListener eventListener) {
        this.eventListener = eventListener;
    }

    private RenderEvent createRenderEvent(String methodName) {
        RenderEvent event = new RenderEvent();
        event.setMethodName(methodName);
        event.addProp(new Object[] { "containingBPPosition", this.containingBPPosition });
        event.addProp(new Object[] { "containingIPPosition", this.containingIPPosition });
        event.addProp(new Object[] { "currentBPPosition", this.currentBPPosition });
        event.addProp(new Object[] { "currentIPPosition", this.currentIPPosition });
        return event;
    }

    private void pushEvent(RenderEvent event) {
        this.eventListener.fireEvent(event);
        if (this.eventStack.size() > 0) {
            this.eventStack.peek().addChildEvent(event);
        }
        this.eventStack.push(event);
    }

    private void popEvent(RenderEvent event) {
        if (this.eventListener != null) {
            RenderEvent topEvent = this.eventStack.pop();
            if (topEvent != event) {
                throw new RuntimeException("status error");
            }
        }
    }

    public void startRenderer(OutputStream outputStream) throws IOException {
        if (this.eventListener != null) {
            this.eventStack = new Stack<RenderEvent>();
            this.rootEvent = this.createRenderEvent("startRenderer");
            this.pushEvent(this.rootEvent);
        }
        super.startRenderer(outputStream);
    }

    public void stopRenderer() throws IOException {
        if (this.eventListener != null) {
            this.popEvent(this.rootEvent);
            this.eventListener.finishRender(this.rootEvent);
            this.eventStack = null;
            this.rootEvent = null;
        }
        super.stopRenderer();
    }

    public void startPageSequence(PageSequence pageSequence) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("startPageSequence");
            this.pushEvent(event);
        }
        try {
            super.startPageSequence(pageSequence);
        } finally {
            this.popEvent(event);
        }
    }

    protected void renderPageAreas(Page page) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderPageAreas");
            this.pushEvent(event);
        }
        try {
            super.renderPageAreas(page);
        } finally {
            this.popEvent(event);
        }
    }

    public void renderPage(PageViewport page) throws IOException, FOPException {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderPage");
            this.pushEvent(event);
        }
        try {
            super.renderPage(page);
        } finally {
            this.popEvent(event);
        }
    }

    protected void renderRegionViewport(RegionViewport viewport) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderRegionViewport");
            this.pushEvent(event);
        }
        try {
            super.renderRegionViewport(viewport);
        } finally {
            this.popEvent(event);
        }
    }

    protected void renderBodyRegion(BodyRegion region) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderBodyRegion");
            this.pushEvent(event);
        }
        try {
            super.renderBodyRegion(region);
        } finally {
            this.popEvent(event);
        }
    }

    protected void renderMainReference(MainReference mr) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderMainReference");
            this.pushEvent(event);
        }
        try {
            super.renderMainReference(mr);
        } finally {
            this.popEvent(event);
        }
    }

    protected void renderFlow(NormalFlow flow) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderFlow");
            this.pushEvent(event);
        }
        try {
            super.renderFlow(flow);
        } finally {
            this.popEvent(event);
        }
    }

    protected void renderBlocks(Block parent, List blocks) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderBlocks");
            this.pushEvent(event);
        }
        try {
            super.renderBlocks(parent, blocks);
        } finally {
            this.popEvent(event);
        }
    }

    protected void renderBlock(Block block) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderBlock");
            this.pushEvent(event);
        }
        try {
            super.renderBlock(block);
        } finally {
            this.popEvent(event);
        }
    }

    protected void renderLineArea(LineArea line) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderLineArea");
            this.pushEvent(event);
        }
        try {
            super.renderLineArea(line);
        } finally {
            this.popEvent(event);
        }
    }

    protected void renderInlineArea(InlineArea inlineArea) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderInlineArea");
            this.pushEvent(event);
        }
        try {
            super.renderInlineArea(inlineArea);
        } finally {
            this.popEvent(event);
        }
    }

    public void renderInlineParent(InlineParent ip) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderInlineParent");
            this.pushEvent(event);
        }
        try {
            super.renderInlineParent(ip);
        } finally {
            this.popEvent(event);
        }
    }

    protected void renderBlockViewport(BlockViewport bv, List children) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderBlockViewport");
            this.pushEvent(event);
        }
        try {
            super.renderBlockViewport(bv, children);
        } finally {
            this.popEvent(event);
        }
    }

    protected void renderText(TextArea text) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderText");
            event.addProp(new Object[] { "text", text.getText() });
            this.pushEvent(event);
        }
        try {
            FontTriplet font = (FontTriplet) text.getTrait(CustomTextLayoutManager.TRAIT_ORIGIN_FONT);
            originFont.set(font);
            super.renderText(text);
        } finally {
            originFont.set(null);
            this.popEvent(event);
        }
    }

    protected void renderWord(WordArea word) {
        RenderEvent event = null;
        if (this.eventListener != null) {
            event = this.createRenderEvent("renderWord");
            this.pushEvent(event);
        }
        try {
            super.renderWord(word);
        } finally {
            this.popEvent(event);
        }
    }
}
